#include <iostream>
#include "Tokenizer.h"
#include "common.h"
const string symbolStrings[] = 
{
    "<no token>", "<IDENTIFIER>", "<NUMBER>", "<STRING>",
    "^","*","(",")","-","+","=","[","]",":",";",
    "<",">",",",".","/",":=","<=",">=","<>","..",
    "<END OF FILE>", "<ERROR>",
    "AND","ARRAY","BEGIN","CASE","CONST","DIV","DO","DOWNTO",
    "ELSE","END","FILE","FOR","FUNCTION","GOTO","IF","IN",
    "LABEL","MOD","NIL","NOT","OF","OR","PACKED","PROCEDURE",
    "PROGRAM","RECORD","REPEAT","SET","THEN","TO","TYPE","UNTIL",
    "VAR","WHILE","WITH",
};

typedef struct
{
    std::string     str;
    TokenCode       tCode;    
} 
ReservedWord;

const ReservedWord resWordsLengthTwo[] = 
{
    {"do", DO}, {"if", IF}, {"in", IN}, 
    {"of", OF}, {"or", OR}, {"to", TO}
};
const ReservedWord resWordsLengthThree[] = 
{
    {"and", AND}, {"div", DIV}, {"end", END}, {"for", FOR},
    {"mod", MOD}, {"nil", NIL}, {"not", NOT}, {"set", SET},
    {"var", VAR}
};
const ReservedWord resWordsLengthFour[] = 
{
    {"case", CASE}, {"else", ELSE}, {"file", FFILE},
    {"goto", GOTO}, {"then", THEN}, {"type", TYPE},
    {"with", WITH}
};
const ReservedWord resWordsLengthFive[] = 
{
    {"array", ARRAY}, {"begin", BEGIN}, {"const", CONST},
    {"label", LABEL}, {"until", UNTIL}, {"while", WHILE},
};
const ReservedWord resWordsLengthSix[] = 
{
    {"downto", DOWNTO}, {"packed", PACKED}, 
    {"record", RECORD}, {"repeat", REPEAT}
};
const ReservedWord resWordsLengthSeven[] = {{"program", PROGRAM}};
const ReservedWord resWordsLengthEight[] = {{"function", FUNCTION}};
const ReservedWord resWordsLengthNine[] = {{"procedure", PROCEDURE}};

const ReservedWord* reservedWordTable[] = 
{
    NULL, NULL, resWordsLengthTwo, resWordsLengthThree, 
    resWordsLengthFour, resWordsLengthFive, resWordsLengthSix, 
    resWordsLengthSeven, resWordsLengthEight, resWordsLengthNine,
};


/**
 * Constructor. This initializes the variables.                        
 *
 * @param  aScanner     This is the scanner we are using so this class can access its methods.
 */
Tokenizer::Tokenizer(Scanner &aScanner)
{
    /**
     * This records the type of token that is created.
     */
    code = NO_TOKEN;
    /**
     * This is the literal object that is created.
     */
    lit = NULL;
    /**
     * This is the string that contains the word, symbol, or number.
     */
    tokenString = "";
    /**
     * This is the scanner we are using so this class can access its methods.
     */
    scanner = &aScanner;
}

/**
 * Destructor. We do not call this anywhere in the program.
 *
 * @param  none           
 * @return none
 */
Tokenizer::~Tokenizer()
{
    code = NO_TOKEN;
    lit = NULL;
    tokenString = "";
    scanner = NULL;
}

/**
 * This scans the code and creates the proper literal.                           
 *
 * This function scans the first character and then calls GetCharType to find out what it is.
 *  Then it calls a Get...() function to make the appropriate literal.
 *
 * @param  charCode[]      This variable is just passed to GetNumber and GetWord.
 * @return none
 */
void Tokenizer::GetToken(CharCode charCode[])
{
    SkipBlanks();
    char currentChar = scanner->GetCharacter();
    
    if (currentChar == '\''){
        GetString(currentChar);
    }
    else if (scanner->GetCharType(currentChar) == SPECIAL)
    { 
        GetSpecial(currentChar);
    }
    else if (scanner->GetCharType(currentChar) == DIGIT)
    {
        GetNumber(currentChar, charCode);
    }
    else if (scanner->GetCharType(currentChar) == LETTER)
    {
        GetWord(currentChar, charCode);
    }
}

/**
 * This returns the type of token.
 *
 * @param  none            
 * @return code     This variable holds the information on what type the token is.
 */
TokenCode Tokenizer::GetTokenCode()
{
    return code;
}

/**
 * This returns the string that the token is made from.
 *
 * @param  none            
 * @return tokenString      The string that the token is made from
 */
string Tokenizer::GetTokenString()
{
    return tokenString;
}

/**
 * This returns the literal object that was made.                          
 *
 * @param  none            
 * @return lit      This is the literal object that is created.
 */
Literal* Tokenizer::GetLiteral()
{
    return lit;
}

/**
 * This skips over the blanks to find the first non-space character.                           
 *
 * Added this method because we found this to be an easy way to skip blanks. It calls 
 * GetCharacter until it finds a character that is not a space or tab, and then backs up
 * to before it.
 *
 * @param  none           
 * @return none
 */
void Tokenizer::SkipBlanks()
{
    while(true)
    {
		char ch = scanner->GetCharacter();
		if(ch != ' ' && ch != '\t') break;
	}
    scanner->BackUpCharacter();
}

/**
 * Reads what word is there, changes it to lowercase, and checks to see if it is a reserved word.                        
 *
 * It reads one character at a time and if it is a letter, it makes it lowercase. It goes until
 * it finds a whitespace and then stored the string it has created one character at a time.
 * It checks this word to see if it matches a set of predetermined reserved words. If it is not,
 * then is stores IDENTIFIER as the code variable.
 *
 * @param  ch       this is the character that is returned from GetCharacter
 * @param  table    This variable has the list of reserved words.
 * @return none
 */
void Tokenizer::GetWord(char ch, CharCode table[])
{
    scanner->BackUpCharacter();
    /**
     * This is a temporary string in which we store the characters we get. At the end we store
     * it as tokenString
     */
	string aCString = "";

    while(isalpha(scanner->GetCharacter()))
    {
        scanner->BackUpCharacter();
        aCString += tolower(scanner->GetCharacter());
    }
    
    scanner->BackUpCharacter();
    //this assigns the word to tokenString in lowercase
    tokenString = aCString;
    if (IsReservedWord(tokenString) == false) code = IDENTIFIER;
}

/**
 * Makes a new lieral, then reads the number one character at a time and stores the string.
 *
 * @param  ch       this is the character that is returned from GetCharacter
 * @param  table    This variable has the list of reserved words.
 * @return none
 */
void Tokenizer::GetNumber(char ch, CharCode [])
{
    lit = new Literal(INTEGER_LIT);
    code = NUMBER;
    tokenString = "";
    do
    {
        //write ch into tokenString
        tokenString += ch;
        ch = scanner->GetCharacter();
    } while ( ch == '1' || ch == '2' || ch == '3' || ch == '4' ||  ch == '5' || ch == '6' || ch == '7' || ch == '8'|| ch == '9' || ch == '0' || ch == 'e' || ch == '.' || ch == '-' );
	scanner->BackUpCharacter();
    //continues to parse through the number until it ends.
}

/**
 * Scans the input file one character at a time until it finds a quote, and stores the string                           
 * and creates a string literal.
 *
 * @param  ch       this is the character that is returned from GetCharacter           
 * @return none
 */
void Tokenizer::GetString(char ch)
{
	tokenString = "";
    char prevCh;
    
	while(true)
    {
        prevCh = ch;
        ch = scanner->GetCharacter();
        
        if(ch == '\'' && prevCh != '\\') break;
		else tokenString += ch;
    }

    lit = new Literal(STRING_LIT);
    
    code = STRING;
}

/**
 * Checks the character to see if it is a special character. If it is, it sets the 
 * tokenCode and TokenString.
 *
 * @param  currentChar      this is the character that is returned from GetCharacter
 * @return none
 */
void Tokenizer::GetSpecial(char currentChar)
{
    int i;
    for (i = 5; i <=19; i++)
    {
        if(currentChar == symbolStrings[i][0])
        {
            if (currentChar == ':')
            {
                if (scanner->GetCharacter()=='=')
                {
                    code = (TokenCode)20;
                    tokenString = symbolStrings[20];
                } else {
                    scanner->BackUpCharacter();
                    code = (TokenCode)i;
                    tokenString = symbolStrings[i];
                }
            } else {
            code = (TokenCode)i;
            tokenString = symbolStrings[i];
            }
        }
    }
}

/**
 * This takes a word as an argument and returns true if is a reserved word.                           
 *
 * It compares the passed string to the list of reserved words of the same length. If it finds
 * a match, it sets the TokenString and code variables, and then return true. If not, it
 * returns false.
 *
 * @param &word     This is the string that we are checking to see if it's reserved.
 * @return boolean value of whether of not if found a match.
 */
bool Tokenizer::IsReservedWord(string &word)
{
    //cout << "in IsReservedWord" << endl;
    //cout << "our word is " << word << word.length() << endl;
    
    //check if the word is one of the reserved words specified in the beginning 
    //this class, if true - set code to the appropriate value
    
    //I saw the reservedWordTable array too late, so I used switch statement 

    switch (word.length()){
        case 2:
            for(int k = 0; k < 6; k++)
            {
                if(word.compare(resWordsLengthTwo[k].str) == 0){
                    code = resWordsLengthTwo[k].tCode;
                    tokenString = resWordsLengthTwo[k].str;
                    return true;
                }
            }
			break;
        case 3:
            for(int k = 0; k < 9; k++)
            {
                if(word.compare(resWordsLengthThree[k].str) == 0){
                    tokenString = resWordsLengthThree[k].str;
                    code = resWordsLengthThree[k].tCode;
                    return true;
                }
            }
			break;
        case 4:
            for(int k = 0; k < 7; k++)
            {
                if(word.compare(resWordsLengthFour[k].str) == 0)
                {
                    tokenString = resWordsLengthFour[k].str;
                    code = resWordsLengthFour[k].tCode;
                    return true;
                }
            }
			break;
        case 5:
            for(int k = 0; k < 6; k++)
            {
                if(word.compare(resWordsLengthFive[k].str) == 0)
                {
                    tokenString = resWordsLengthFive[k].str;
                    code = resWordsLengthFive[k].tCode;
                    return true;
                }
            }
			break;
        case 6:
            for(int k = 0; k < 4; k++)
            {
                if(word.compare(resWordsLengthSix[k].str) == 0)
                {
                    tokenString = resWordsLengthSix[k].str;
                    code = resWordsLengthSix[k].tCode;
                    return true;
                }
            }
			break;
        case 7:
            if(word.compare(resWordsLengthSeven[0].str) == 0)
            {
                tokenString = resWordsLengthSeven[0].str;
                code = resWordsLengthSeven[0].tCode;    
                return true;
            }
            break;
        case 8:
            if(word.compare(resWordsLengthEight[0].str) == 0)
            {
                tokenString = resWordsLengthEight[0].str;
                code = resWordsLengthEight[0].tCode;    
                return true;
            }
			break;
        case 9:
            if(word.compare(resWordsLengthNine[0].str) == 0)
            {
                tokenString = resWordsLengthNine[0].str;
                code = resWordsLengthNine[0].tCode;    
                return true;
            }
			break;
    }
	return false;
    
}
